8086 CPU :: INSTRUCTION & DATA FLOW תקציר זוהי מצגת נסיונית בנושא אסמבלר, 8086 בעקבות תובנות של נסיונות לימוד של מי ש"איננו בעשירון העליון" מטרותיה : (TD) "סיור מודרך" על דוגמה איך משתמשים בטורבו-דבאגר (בכוונת מכוון בנוסח "ואידך זיל גמור" ( CALL JMP נסיון להדגים ישירות על המכונה תהליך ("לחימום"), ותהליך עם העברת פרמטרים לייצר "רב שיח" בין מורים/עוזרי הוראה לגבי השפעה של טכניקה זו או אחרת: על קליטה החומר על המוטיבציה לנצל כלים כדי להפוך לאוטו-דידקטים ("המטרה ארוכת הטווח בהא הידיעה") 5 מרץ 15 כתב: יגאל שפירא (עוזר הוראת אסמבלר - גבהים) 1
1. רבי עקיבא אמר: "ואידך זיל גמור". תרגום לעברית: "והשאר לך ולמד" (בעצמך!). 2 "לא נכשלתי 10000 פעם, אבל מצאתי 10000 שיטות שלא עובדות" - (אדיסון אחרי שמצא את החומר הנכון לנורה החשמלית) CPU :: INSTRUCTION & DATA FLOW 8086 מבוא לפני שבוע, במסגרת מפגש עוזרי הוראה, דיברנו על "התרגלו להאכלה בכפית", כשנשאלתי כיצד אני מנסה להשפיע, עניתי : "כמו עם הנכד". הוא שואל משהו, אני עונה "ומה אתה חושב?" במחשבה שניה אחרי המפגש, הכרזתי על הטכניקה הזו כטעות (עוד אחת מיני 10000 בדרך למצוא את שיטת הלימוד המתאימה לי ולהם). הסיבה: אצל הנכד הזאטוט אני בחזקת סמכות עליונה. כשאני שואל הוא מתאמץ לחשוב בכיתה מי אני בשבילם? אפילו לא מורה, רק עוזר. לא נותן ציון. אשפיע על העתיד? מי מתעניין שם בעתיד? פלא שהתשובה היא "נו תגיד כבר ודי" לפני יומיים קיבלתי את התובנות משיעור למתחילים בחאן-אקדמי. (גם ניסיתי אישית מרשים עד מדהים!!) התובנה שלי: הנה הם מסוגלים ללמוד לבד, ומקבלים בתמורה את מה שחסר בשיעורי האסמבלר שלנו : סיפוק מיידי ועכשיו. (גם אנחנו המבוגרים אוהבים לצייר. וכאן גם אפשר לעשות PAUSE באמצע שג'סיקה מדברת, לשחק בציור בשביל הכף, ולהמשיך שוב..) במחשבה שניה: ג'סיקה לא זרקה אותם למים ונתנה להם לחפש לבד (*) היא כן מתחילה בהאכלה בכפית קטנה, מראה להם שזה טעים, ומראה שבמרחק 10 סמ' למטה יש עוד "GOODIES" מעודדת לנסות. ןלכן אני מרים "בלון ניסוי" : הדגמה "הכי קרוב להדגמה חיה ", שניתן גם ללמוד עצמית ברור שהנ"ל רחוק ממלוטש (וגם אם ילוטש אין לי וודאות שישיג את המטרות הנכספות). אבל זה יכול לשפר את הרב-שיח לכן כל תגובה (או התנסות ותגובה) של מורה/עוזר תבורך, ואם מישהו טרם העביר את השיעור וירצה לנסות על תלמידים יבורך כפליים (*) סוגטה מיטרה כן ניסה את שיטת "זרוק למים" וזה הצליח (לפחות בתרבות ההודית) מצרף כאן 3 לינקים מתוך שלל מצגות שלו (הראשונה לטעמי היא החשובה יותר בענייני תובנות של "מה אני רוצים להשיג") https://www.ted.com/talks/sugata_mitra_build_a_school_in_the_cloud?language=he.1.2.3 2
8086 CPU :: INSTRUCTION & DATA FLOW להלן שתי דיאגרמות דומות שמנסות לתאר את החיבורים בתוך אותו המעבד ומדוע זה צריך לעניין? ) את התוכניתנים ואנשי הסייבר... ( התשובה : בערך מאותה הסיבה שנהג צריך להבין מה קורה כאשר : * בולמים פתאומית * נדלקת נורת אזהרה * לא מבינים מה קורה במצב גיר ידני ("קלאסי", או טיפטרונקס) * נשארים עם בלם יד משוך ההסבר לתשובה: בחלקם יכולים לשפר ביצועים, בכולם עלולים להזיק למכונית (ולנוסעים) 3
8086 CPU :: INSTRUCTION & DATA FLOW בהמשך מצגת זו : תיאור עם אנימציות של פעילויות שונות לכן : הפעל במוד מצגת!! (אחרת סדר הפעולות לא נראה כלל) במקרה של מצגת PPS (שרצה ללא מגע יד אדם): תוכל לעצור ולגלגל אחורנית ע"י עכבר (כדי להתעכב או לחזור על קטע) 4
8086 CPU :: INSTRUCTION & DATA FLOW מטרות מרכזיות של הצגת זרימת הפקודות והנתונים (! "להכניס טוב לראש" שמחשב לא חושב (רק פותח וסוגר ברזים לפי תכנית - כמו מכונת כביסה "להרגיש" מגבלות כגון : מדוע אין יכולת ישירה לעשות את כל הפעולות עם כל הרגיסטרים מדוע יש רק כפל של 8 ביט ב, 8 של 16 ב 16 אבל לא שניהם להבין דרך המגבלות הנ"ל את הודעות השגיאה של האסמבלר TASM) ( ) כי כשאני רואה את המכונה לנגד עיני, הבנת הודעת השגיאה מקצרת את "זמן תיסכול"!! ( 5
8086 CPU :: INSTRUCTION & DATA FLOW בפקודת-מכונה יש סדרת פעולות : הבאת הפקודה מהזכרון פענוח הצבת כתובות, [הבאת עוד בתים] הבאת משתנים, שליחה ל,ALU שמירת תוצאות [רגיסטר, זכרון] קביעת מקום הפקודה הבאה הסדרה הנ"ל היא למעשה: פתיחה וסגירה של "ברזים", המידע רץ ב "אוטוסטרדות" יש מרכזיה קטנה ליד כל אלמנט חומרה יש "גמד קטן" שמבצע את הסדרה של פתיחת וסגירת השסתומים לפי סדר איזו פעולה מבוצעת ע"י פתיחת שני ברזים אלו? איזו ע"י שני ברזים אלו? זכרון וקלט פלט ואיזו ע"י שני אלו? ה"גמד" בעצמו הוא: דוגמה לבורר שני ביטים בין 4 רגיסטרים "תת-מיקרו-פרוססור" יש לו הוראות נוסח "ספר - בישול" (צרוב ללא יכולת שינוי), שמושפע ע"י החומרה הבאה: רגיסטר הפקודה רגיסטר דגלים קוי פסיקה מחומרות נוספות (אנחנו המשתמשים טוענים קוד, שרץ במכונה) "ספר הבישול" של הבקר נקרא מיקרו-קוד, 6 הוא שולט ישירות על כל המתגים שבמעבד זה מקום מושבו (יחידת הבקרה)
8086 JMP COMMAND מטרות מרכזיות של הצגת תהליך JMP "להכניס טוב לראש" שמחשב לא חושב ולא מנחש ) אלא עושה רק את הפקודה הכתובה) "להרגיש" מגבלות והשלכות כגון : מגבלות טווח קפיצה השלכות אפשריות למקרי "התחכמות" של שינוי קוד תוך (לא רק תחרויות ווירוסים, יש למשל נישה של כדי ריצה אפליקציות של אבטחה מסיבות מסחריות האמינות התפעולית מאד חשובה שם) 7
8086 JMP ZERO COMMAND - 2 נקח למשל את הפקודה JZ (קפוץ לכתובת רק אם דגל Z מורם) 15h. 11h לכתובת בתכנית הזו לדוגמה JZ בכתובת ", לאן? : 02 JZ הפקודה היא מדוע:? ( רגיסטר כתובת הפקודה IP סיים לקרוא את הפקודה בשורה 11 ו- 12, IP יקדם עצמו אוטומטית ל - 13h לפקודה העוקבת. אבל אם 1=Z, במקום לבצע אותה, לשנות את IP ל-! 15h הוא צריך ומה אומרת הפקודה למכונה (בשפת מכונה מתורגם להפעלת מתגים בסיליקון " אם 1=Z, אז תוסיף ל 2 IP לפני שתקרא את הפקודה הבאה " שהוא בתחום +127 to -128 ומה כתוב בספרות על מבנה פקודת? JZ שהיא בת שני בתים, שהראשון הוא 74h disp שהשני מכיל את (displacement) 8
( 8086 JMP COMMAND - 3 - לא חיוני ( : היכן מתבצע הסיכום? ("בונוס" (לא מאד חשוב למתכנת לכל היותר הוא רק צריך לדעת אורך פקודה ומספר שעונים לביצוע ( (כן חשוב למתכנן החומרה הוא מתחרה על נתח שוק, מנסה לעשות זול אמין זריז - גמיש ב? 8086 לא יודע, אבל מתחשק לי לנחש:, ההסט DI מחכה בכניסה למפענח הכתובות, אחרי שהבאנו את הבית הראשון (74h) (Jump NOT taken IP כבר הועלה ל "כתובת המחדל" (של הפקודה הבאה אם ו - אם מבוצע סיכום, יש שתי אפשרויות סיכום במסכם הכתובות או שמועבר מכניסת מפענח הפקודה או שהוכנס "ישירות מהצד" בצד ימין (שם יש פס נתונים ברוחב 8 ביט)? או סיכום ב?? ALU מנחש שלא (כי זה לוקח יותר שעונים: להעביר IP של 16 ביט ולפחות 17 בדרך חזרה) (אבל אלו שיקולים של מתכנן השבב) 9
8086 JMP COMMAND : מגבלות והשלכות מגבלות טווח קפיצה השלכות אפשריות למקרי "התחכמות" של שינוי קוד תוך (לא רק תחרויות ווירוסים, יש למשל נישה של כדי ריצה אפליקציות של אבטחה מסיבות מסחריות האמינות התפעולית מאד חשובה שם) 10
8086 CALL PROCESS - 1 מטרות מרכזיות של הצגת תהליך CALL אימון בשימוש ב TD "לראות לCPU את הלבן בעיניים" : הבנת התהליך - לצורך "צמצום זמן תיסכול " מניעת "טעויות מתכנת" הכרת מנגנון ה CPU (החומרה שמאחורי): משפר הבנה של הודעות שגיאה, מאפשר יישום טכניקות לבדיקה טובה של הקוד בזמן הפיתוח: הבנת הקוד עוזרת לתכנון סט בדיקות יעיל דיבוג של קוד טרי קל יותר לפני שנשכח מה כתבנו ולמה : להוביל להבנת מגבלות והשלכות כגון מגבלות טווח קפיצה זכרון" "זליגת, התברברות, ונזקיהן חולשות (אבטחה) 11
8086 CALL PROCESS - 2 הצגת דוגמה פרטנית צעד צעד של פניה לפונקציה, הדגמה עם תכנית פשוטה והרצה צעד צעד בעזרת TD עם שליחה והחזרת מידע דרך המחסנית mov ax, @data (DS==SS) 28h, IP = 07, (התכנית נטו מתחילה בכתובת התחלתי בכתובת תובנה צדדית "לקחי תפעול" : שינוי stack segment ועצירה תוך כדי תצוגת דה-באג משבשת קלות. העקיפה: החל את התכנית עם 3 צעדים ראשונים בסדר הבא: mov SS, ax ; ss 1 st mov DS, ax ;You completed this one after pressing F7 3 times, ; Now you are ready to start display CPU pane {by <alt-v>, <C> } {or by mouse} ; 1 st real code starts here (IP=7, The 1 st 3 op-codes are overhead ) ================= B. CODE HERE ================= ; (v1,v2] (ans = max_byte (התכנית הראשית: בשפה גבוהה ; sum v1-3 ByVal: push [ANS] ; -- MAin must read result by POP () push [word v1] ; --pushed 1 extra byte (ignored in sub) - Stack release inside the sub push [word v2] ; ----pushed 1 extra byte (ignored in sub) - Stack release inside the sub call Max_Byte ; returns unsigned bigger byte pop [ANS] ; two bytes, msbyte ignored ; ===================================================== כדי ללמוד מזה לנצל את TD למטרתו (כדה-באגר), מומלץ לצפות קודם בסרטון שמראה את ההפעלה ברצף, ואז לעבור דרך השקפים הקובץ הוא : Call_example_View_by_TD_session.avi 12
mov ax, @data 8086 CALL PROCESS - 3 תחילת התכנית: (עצה: אם תשנה את SS בתחילת תכנית עשה זאת בסדר שמופיע פה) mov SS, ax ; ss 1 st mov DS, ax ;You completed this one after pressing F7 3 times, ; Now you are ready to start display CPU pane {by <alt-v>, <C> } {or by mouse} ================= B. CODE HERE ================= ; (v1,v2] (ans = max_byte (התכנית הראשית: בשפה גבוהה ; sum v1-3 ByVal: push [ANS] ; -- MAin must read result by POP () push [word v1] ; --pushed 1 extra byte (ignored in sub) - Stack release inside the sub push [word v2] ; ----pushed 1 extra byte (ignored in sub) - Stack release inside the sub call Max_Byte ; returns unsigned bigger byte pop [ANS] ; two bytes, msbyte ignored ; ===================================================== ====================== MAX_BYTE ================= ; הפונקציה : proc Max_Byte ; UNSIGNED (ByVal A db,byval B db,byval C db ) push bp mov bp, sp pushf ; flags, and all registers that will be changed are pushed here push AX ; (using this reg, RESTORE LATER) ;======== Procedure "MEAT": sum 3 params from stack, answer must be pooped by caller MOV AL, [byte BP+ 4] ; "pre-pushed" C (v2) CMP AL, [byte BP+6] ; (AX-B)== (v2-v1) JNB C_gt_B ; C not below leave as is MOV AL, [byte BP+6] ; (AX=B)== (v1) C_gt_B: mov [byte BP+8], AL ;======== end Procedure "MEAT": ===================================== pop AX popf pop bp ret 4 ; leaves ans to be popped by CALLER!! endp Max_Byte 13
8086 CALL PROCESS - 4 תחילת התכנית: הפקודה המוארת היא זו שטרם התבצעה התוצאות הן של הפקודה הקודמת נתרכז ב DATA שהוכנסה (מסומנת בצהוב) 28h מוצב בכתובת (נתרגל שהמידע בכתובת 27h במחסנית היא ה"שאריות" של תכנית ה TD היא "תטייל" עם תזוזת ה STACK שלנו) 14
8086 CALL PROCESS - 5 אחרי דחיפת המשתנה הראשון למחסנית: החץ מראה מי הועתק לאן (ע"י הוזז למטה בשניים ( PUSH ( (אנחנו רואים "הופעת" 18 בתים במחסנית מתחת לכתובת אליה דחפנו מילה זה מידע לא מעניין של TD בתנאי שלא ידרוס את הנתונים שלנו. אנו סופרים 17 בתים בשימוש TD 15
8086 CALL PROCESS - 6 אחרי דחיפת המשתנה השני למחסנית: החץ מראה מי הועתק לאן (ע"י הוזז למטה בשניים ( PUSH ( (אנחנו רואים "הופעת" 18 בתים במחסנית מתחת לכתובת אליה דחפנו מילה זה מידע לא מעניין של TD בתנאי שלא ידרוס את הנתונים שלנו. אנו סופרים 17 בתים בשימוש TD V1 & 2 nd byte 16
8086 CALL PROCESS - 7 אחרי דחיפת המשתנה השלישי למחסנית: (ורגע לפני בצוע ( CALL החץ מראה מי הועתק לאן (ע"י ( PUSH ) 3 המילים הועברו, אנחנו טרם קפיצה) נשים לב לכתובת שתהיה לאחר ביצוע :CALL 16h של הפקודה העוקבת היא IP 1Fh הנדרש כדי לקפוץ לפונקציה הוא IP כדי לקפוץ יש להוסיף 9 ל IP (וזה מה ש"אומר" ה BYTE השני של,CALL שנמצא בכתובת ( 14h הכתובת העוקבת היא זו אליה נצטרך לקפוץ חזרה מהפונקציה. היא תידחף עכשיו למחסנית תוך כדי הקפיצה 17
8086 CALL PROCESS - 8 אחרי הקריאה לפונקציה (קפיצה + דחיפה ( : החץ מראה מי הועתק לאן ) PUSH מובנה ב- CALL ( הוזז למטה בשניים ודחף את כתובת החזרה בתוך הפונקציה נרצה להצביע על המשתנים שנדחפו: (אפשר היה ע"י, POP אבל כתובת החזרה בדרך. והיכן נאחסן אם יש לא 2 אלא 200?) אז נקצה את ה ( BP ) Base Pointer, ונעניק לו את כתובת ה הנוכחי. אבל נרצה להיות נחמדים לתכנית שפנתה אלינו לעזרה, ולא להשמיד לה את תכולת הרגיסטרים, לכן נשמור אותם במחסנית לפני שנשנה את ערכם. 18
8086 CALL PROCESS - 9 (אחרי דחיפת BP המקורי למחסנית): החץ מראה מי הועתק לאן (ע"י הוזז למטה בעוד שניים ( PUSH (במקרה זה BP היה, 0000 במחסנית היה, 0000 העד היחיד הוא שמצביע על 1E) נשים לב שכדי לקרוא משתנה מהמחסנית בקריאת זכרון רגילה, הפרמטר האחרון שהועבר לפונקציה יושב בכתובת. 22h אנו עומדים להעתיק את ל (001Eh) BP עכשיו! (ההפרש בין BP לפרמטר הקרוב יהיה. 4 לו היינו דוחפים קודם את כל שאר הרגיסטרים לשמירה, החשבון היה מקשה עלינו בכתיבת התכנית ( 19
8086 CALL PROCESS - 10 : אחרי העתקת ל BP עכשיו דחיפת דברים נוספים למחסנית לא תשנה את המרחק בין BP לכתובת הארגומנטים שמתקשרים עם התכנית שקראה לפונקציה (ה " לקוח ") (כולל שימוש למשתנים מקומיים, לקריאה לפונקציות אחרות, (... (הפקודה הבאה : שמירת ערך מקורי של הדגלים, לפני שהפונקציה תעשה פעולות שישנו אותו, כך שנוכל לשחזר ביציאה ). [BP+4] [BP+6] [BP+8] == V2 ==V1 == ans 20
8086 CALL PROCESS - 11 (אחרי העתקת 8 ביטים של דגלים למחסנית): הדגלים בעלי ערך, 2 נשמר במחסנית למחסנית נכנס גם בית נוסף ) PUSH זה 16 ביט ( לא רלוונטי (ואולי כן?? אם הייתי האקר הייתי חוקר ע"י ניסוייי נגיעה בו. אבל כתכנת "רגיל" - לא כדאי לחשוב על זה בכלל)? 21
8086 CALL PROCESS - 12 עכשיו שימרנו גם את הערך המקורי של AX בצעד הבא נשלוף את המשתנה השני מהמחסנית השליפה כקריאת זכרון מ [BP+4] (במקור: V2) ואז נשווה למשתנה הראשון מהמחסנית הפנייה כקריאת זכרון מ [BP+6] (במקור: V1) 22
8086 CALL PROCESS - 13 אחרי משיכת המשתנה השני מהמחסנית : החץ מראה מי הועתק לאן [bp+4] (MOV AL, (ע"י הבית השלישי בפקודת MOV (כתובת 26h) מעיד על כך שהאופסט הוא 4 22h 22 23
8086 CALL PROCESS - 14 אחרי השוואת המשתנה הראשון ל : AL החיצים מראים מי השפיע על מצב הדגלים, שמראים ש AL הוא הגדול או שווה מראה (ולכן ניתן לדלג על הפקודה שתחליף אותו) (גם פה ניתו לראות שהפניה [bp+6] לוקחת את המספר 6 מהבית השלישי של הפקודה (בכתובת ( 0029h ולכן אנחנו צופים שה JMP יתבצע (ידלג על הפקודה העוקבת בת 3 בתים, לכן יש להוסיף 3 ל, IP וזה מה שמצוין בבית השני של הפקודה (כתובת (2Bh 24
8086 CALL PROCESS - 15 אכן הקפיצה בוצעה (דילגנו על פקודת MOV אחת) עומד להחזיר את AL למחסנית, למקום שהכנו מראש ע"י ה PUSH הראשון (זה שבאחריות הקורא למשוך ע"י ( POP 25
8086 CALL PROCESS - 16 ואכן AL (התוצאה) הועבר למחסנית, מיקומו בתא הראשון שנדחף ע"י התכנית הקוראת (ואנו עומדים להחזיר את AX המקורי מהמחסנית) 26
8086 CALL PROCESS - 17 והחזיר את AX המקורי מהמחסנית למחסנית: (לא להתבלבל הנתון נמחק מהמחסנית רק משום ש TD החליט מיד אחרי הביצוע שהתא פנוי והוא יכול "לפלוש לדירה הריקה" כדי לבצע את "עבודות השירות" (popf ועומד לשחזר את הדגלים המקוריים (במקרה ערכם ( 02 (הפקודה הבאה: 27
8086 CALL PROCESS - 18 והדגלים שוחררו גם, (וגם הפעם TD השתלט מיד על השטח המשוחרר ודרס ( ועומד לשחזר את BP (ערכו עדין ( 1Eh מהמחסנית: 28
8086 CALL PROCESS - 20 אכן גם BP שוחזר, והגענו לרגע הגדול : מצביע על כתובת הקפיצה חזרה שלש מילים נדחפו למחסנית מחוץ לפונקציה. וקשה לעשות PUSH מתוך הפונקציה המתכננים הלכו לקראתנו ומימשו "קיצור דרך " RET+4 יתורגם לשפת מכונה שפרושה: ".. והקפץ את 4 בתים נוספים" ) ואנו רואים את זה בבית השני של הפקודה בכתובת ( 0036h 29
ו( 8086 CALL PROCESS - 21 אכן הקפיצה חזרה מהפונקציה הושלמה 6 הבתים ששוחררו מהמחסנית נדרסו ע"י TD תוך כדי מתן "שרות מסירת דין וחשבון למפעיל") מצביע על כתובת הנתון שטרם נשלף (26h ) (הערך המקורי של היה 28h כלומר: "עוד POP וגמרנו") ואנו לפני ביצוע הפקודה האחרונה שעומדת להחזיר את התשובה מהמחסנית לכתובת קבועה בזכרון: 30
8086 CALL PROCESS - 22 השליפה האחרונה הסתיימה התוצאה במקום (נכון ש TD דרס... התרגלנו ל "אנומליה" הזו) חזר לערכו המקורי (חשוב לוודא או שתהיה "זליגת זכרון") שאר הרגיסטרים חזרו לערכם המקורי (מדוע למי ולמה זה חשוב?) והתכנית יכולה להמשיך בדרכה ) במקרה הזה ממשיכה לאן?) 31
8086 CALL PROCESS - 23 סיכום מבוא הזכיר לנו שהבנת מבנה המכונה משפיע על יכולתנו "ללחוץ על הכפתורים הנכונים" ) CALL עם העברת פרמטרים במחסנית ( : Turbo Debugger הודגם השימוש ב לצרך הבנת תהליכים הרצה על מקרה קל (JMP) הדגמה של מקרה מורכב יותר הודגמו כמה " טכניקות וטריקים " לצורך עבודה יעילה עם ה TD ה"בונוס" : הנ"ל מסייע לצורך: (מהלך תוכנה, או שינוי ערכים באמצע) אימות ובדיקת קוד דיבוג תקלות בזמן פיתוח למשל בעת תחזוקת קוד של עמית) "הנדסה לאחור" (למשל בתחרות. הערות יש יתרון להדגמה על "הדבר האמיתי" החסרון : הטרחה (באמצעים הקיימים ( עצומה הטכניקה הקלה יותר היא שקפים שמדגימים : אז אחרי שהשתכנענו : נוכל לעבור אליה 32
8086 CALLPROCESS - 24 מניעת טעויות, 4AC1 PROGRAM AREA CALL Get-DATA NEXT CMD ** ** ** ** ** EXIT Get-DATA: Read,Read,Read Return ( PUSH ) Return address 012A 34 2E 12 FC 67 00 D3 0F 4A 01 C1 2A 33 33
טמפלאיט ראשוני להצגה עם אנימציה בשקפים (הצורך: הדגמת תהליך תוכנה ב CPU קל ליצירה) הארה: צורך הוא פתרון לצורך "בסיסי" מסויים. הצורך הזה קיים עד אשר הבעייה הראשונית נעלמה, או שגילינו פתרון טוב יותר... 34